﻿using System;
using System.Text;
using System.Reflection;
using System.Ext.Xml;
using System.Xml;
using Microsoft.SPOT;

namespace mfXmlSerializer
{
    /// <summary>
    /// represents an abstract element in serialzation map
    /// </summary>
    public abstract class mfMapBaseElement : IDisposable
    {

        #region CTOR
        /// <summary>
        /// ctor 4 new map element
        /// </summary>
        public mfMapBaseElement()
        {
        }

        /// <summary>
        /// ctor 4 new map element
        /// </summary>
        /// <param name="name">name of this (xml-)element</param>
        public mfMapBaseElement(string name)
        {
            InitializeName(name);
        }


        /// <summary>
        /// specialized ctor 2 init ALL properties
        /// for use in derived classes
        /// </summary>
        /// <param name="name">name of this (xml-)element</param>
        /// <param name="setMethod">reflected set-method</param>
        /// <param name="getMethod">reflected get-method</param>
        /// <param name="childMap">child map </param>
        public mfMapBaseElement(string name, MethodInfo setMethod, MethodInfo getMethod, mfSerializationMap childMap)
        {
            InitializeName(name);

            SetMethod = setMethod;
            GetMethod = getMethod;
            ChildMap = childMap;
        }

        #endregion

        #region Properties
        /// <summary>
        /// name of this element
        /// </summary>
        public String Name { get; set; }
        /// <summary>
        /// reflected setter-method
        /// </summary>
        public MethodInfo SetMethod { get; set; }
        /// <summary>
        /// reflected getter-method
        /// </summary>
        public MethodInfo GetMethod { get; set; }
        /// <summary>
        /// type object of this element
        /// </summary>
        public Type ElementType
        {
            get
            {
                if (GetMethod == null)
                {
                    throw new mfXmlException("no get-method for elementtype in " + this);
                }

                return GetMethod.ReturnType;
            }
        }
        /// <summary>
        /// [optional] child map, if SerializationType is class
        /// </summary>
        public mfSerializationMap ChildMap { get; set; }
        /// <summary>
        /// determines, if a child map is available
        /// </summary>
        public bool HasChildMap { get { return (ChildMap != null) ? true : false; } }
        /// <summary>
        /// determines, if all (minimum) required properties are filled
        /// </summary>
        public bool IsComplete { get { return (SetMethod != null && GetMethod != null) ? true : false; } }
        #endregion

        #region Methods

        private void InitializeName(string name)
        {
            if (name == null
                || (name != null && name == String.Empty))
            {
                throw new mfXmlException(GetType() + " needs a valid name");
            }
            Name = name;
        }

        /// <summary>
        /// starts serialization from this element
        /// </summary>
        /// <param name="xmlWriter">xml writer object 2 write on</param>
        /// <param name="target">target object 2 read</param>
        /// <param name="elementSerializedAction">optional, action triggered after this element is serialized</param>
        public void Serialize(XmlWriter xmlWriter, Object target, mfDelElementSerialized elementSerializedAction = null)
        {
            //invoke concrete seralization strategy
            SerializeElement(xmlWriter, target);

            mfXmlTypedSerializer.OnElementSerialzed(elementSerializedAction, xmlWriter, this.Name);
        }

        /// <summary>
        /// starts deserialization from this element
        /// </summary>
        /// <param name="xmlReader">xml reader object 2 read on</param>
        /// <param name="source">source object 2 read</param>
        public void Deserialize(XmlReader xmlReader, Object source)
        {
            if (xmlReader.IsElementInvalid(this))
            {
                Debug.Print("element over-readed: " + xmlReader.Name);
                return;//read next element in Map
            }

            //invoke concrete deseralization strategy
            DeserializeElement(xmlReader, source);
        }

        public void ExecuteSet(Object target, String newValue)
        {
            try
            {
                ExecuteSetElement(target, newValue);
            }
            catch (Exception ex)
            {
                throw new mfXmlException("can't set " + newValue+ " on " + Name, ex);
            }
        }

        protected abstract void SerializeElement(XmlWriter xmlWriter, Object source);

        protected abstract void DeserializeElement(XmlReader xmlReader, Object target);

        protected abstract void ExecuteSetElement(Object target, String newValue);

        protected void SetElement(XmlReader xmlReader, Object targetOfInvoke, Object targetValue)
        {
            //skip element, if no value is available --> optional element-reading-requirement
            if (xmlReader.Value.IsNullOrEmpty())
            {
                return;
            }

            SetMethod.Invoke(targetOfInvoke, new object[] { targetValue });
            
            xmlReader.Read();
            xmlReader.AssertIfEndElementIsInvalid(this);
            xmlReader.Read();
        }

        /// <summary>
        /// overridden and returns name of element
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Name + " : " + GetType();
        }
        #endregion


        #region IDisposable
        public void Dispose()
        {
            this.Name = null;
            this.SetMethod = null;
            this.GetMethod = null;

            if (HasChildMap)
            {
                foreach (mfMapBaseElement child in ChildMap.SerializationElements)
                {
                    child.Dispose();
                }
            }
        }
        #endregion
    }
}
